/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.clone;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.objenesis.strategy.StdInstantiatorStrategy;

/**
 *
 * @author bennyl
 */
public class DeepCopy {

    private static Kryo kryo = new Kryo();
    private static final Set immutableTypesSet;
    private static Method cloneMethod;

    static {
        immutableTypesSet = new HashSet();
        immutableTypesSet.add(Integer.class);
        immutableTypesSet.add(Character.class);
        immutableTypesSet.add(String.class);
        immutableTypesSet.add(Double.class);
        immutableTypesSet.add(Float.class);
        immutableTypesSet.add(Long.class);
        immutableTypesSet.add(Byte.class);
        immutableTypesSet.add(Short.class);
        immutableTypesSet.add(Boolean.class);

        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        kryo.addDefaultSerializer(DeepCopyable.class, new Serializer<DeepCopyable>() {
            @Override
            public void write(Kryo kryo, Output output, DeepCopyable t) {
                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }

            @Override
            public DeepCopyable read(Kryo kryo, Input input, Class<DeepCopyable> type) {
                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }

            @Override
            public DeepCopyable copy(Kryo kryo, DeepCopyable t) {
                return (DeepCopyable) t.deepCopy();
            }
        });

        //adding cloning support
        try {
            cloneMethod = Object.class.getDeclaredMethod("clone");
            cloneMethod.setAccessible(true);
            kryo.addDefaultSerializer(Cloneable.class, new Serializer() {
                @Override
                public void write(Kryo kryo, Output output, Object t) {
                    throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
                }

                @Override
                public Object read(Kryo kryo, Input input, Class type) {
                    throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
                }

                @Override
                public Object copy(Kryo kryo, Object t) {
                    try {
                        return cloneMethod.invoke(t);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            });
        } catch (NoSuchMethodException | SecurityException ex) {
            Logger.getLogger(DeepCopy.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static <T> T copy(T what) {
        if (what instanceof Enum || immutableTypesSet.contains(what)) {
            return what;
        }
        if (what instanceof DeepCopyable) {
            return (T) ((DeepCopyable) what).deepCopy();
        }


        if (what instanceof Cloneable) {
            try {
                return (T) cloneMethod.invoke(what);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                throw new RuntimeException(ex);
            }
        }
        return kryo.copy(what);
    }
}
